package com.sunyuming;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.MessageToMessageDecoder;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.ssl.SslHandler;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;


import javax.net.ssl.KeyManagerFactory;
import java.security.KeyStore;
import java.util.*;

import io.netty.handler.codec.http.*;


public class Boot {

    private static Set<String> printSet = new HashSet<>();
    static {
        printSet.add("kdslife.com");
        printSet.add("libaclub.com");
        printSet.add("zhihu.com");
    }

    private static HttpServerJob.ReqQueue reqQueue = new HttpServerJob.ReqQueue();

    public static void start(final int port) throws Exception {
        EventLoopGroup boss = new NioEventLoopGroup();
        EventLoopGroup worker = new NioEventLoopGroup();
        ServerBootstrap serverBootstrap = new ServerBootstrap();

        try {
            serverBootstrap.channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .group(boss, worker)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
//                            SSLEngine sslEngine = SSLContextFactory.getSslContext().createSSLEngine();
//                            sslEngine.setUseClientMode(false);
//                            pipeline.addLast("SslHandler", new SslHandler(sslEngine));
                            pipeline.addLast("HttpServerCodec", new HttpServerCodec());
                            pipeline.addLast("HttpObjectAggregator", new HttpObjectAggregator(Integer.MAX_VALUE));
                            pipeline.addLast("BodyToResponseEncoder", new HttpServerJob.BodyToResponseEncoder());
                            pipeline.addLast("RequestToBodyDecoder", new RequestToBodyDecoder());
                        }
                    });
            ChannelFuture future = serverBootstrap.bind(port).sync();
            System.out.println("http server start at " + port);
            reqQueue.startConsumer(true);
            future.channel().closeFuture().sync();
        } finally {
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }

    }

    public static final int PORT = 23456;
    public static void main(String[] args) throws Exception {
        start(PORT);
    }

    protected static class SSLContextFactory {

        public static SSLContext getSslContextSuper(String host) throws Exception {
            SSLContext sslContext = SSLContext.getInstance("TLSv1");
            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            KeyStore ks = CertificateGenerator.genkeyStore(host);
            char[] passArray = "changeit".toCharArray();
            kmf.init(ks, passArray);
            sslContext.init(kmf.getKeyManagers(), null, null);
            return sslContext;
        }
    }

    private static class RequestToBodyDecoder extends MessageToMessageDecoder<FullHttpRequest> {

        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("active " + HttpServerJob.open.incrementAndGet());
            super.channelActive(ctx);
        }

        @Override
        protected void decode(ChannelHandlerContext channelHandlerContext, FullHttpRequest fullHttpRequest, List<Object> list) throws Exception {

            if ("CONNECT".equalsIgnoreCase(fullHttpRequest.getMethod().name())) {//HTTPS建立代理握手
                HttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
                channelHandlerContext.writeAndFlush(response);
                System.out.println("ssl request");
                SSLEngine sslEngine = SSLContextFactory.getSslContextSuper(fullHttpRequest.headers().get("host").split(":")[0]).createSSLEngine();
                //SSLEngine sslEngine = SSLContextFactory.getSslContext().createSSLEngine();
                sslEngine.setUseClientMode(false);
                channelHandlerContext.pipeline().addFirst("SslHandler", new SslHandler(sslEngine));
                return;
            }

//            FullHttpResponse httpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
//            httpResponse.content().writeBytes("https".getBytes());
//            httpResponse.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/html;charset=UTF-8");
//            httpResponse.headers().set(HttpHeaders.Names.CONTENT_LENGTH, httpResponse.content().readableBytes());
//            channelHandlerContext.writeAndFlush(httpResponse);
//            if(true)return;

            HttpMethod httpMethod = fullHttpRequest.getMethod();
            String uri = fullHttpRequest.getUri();
            HttpHeaders httpHeaders = fullHttpRequest.headers();

            Map<String, String> map = new HashMap<>();
            for (Map.Entry<String, String> entry : httpHeaders.entries()) {
                map.put(entry.getKey(), entry.getValue());
            }

            ByteBuf msg = fullHttpRequest.content();
            byte[] bs = new byte[msg.readableBytes()];
            msg.readBytes(bs);

            HttpServerJob.OriginHttp originHttp = new HttpServerJob.OriginHttp(httpMethod.name(), uri, map, bs, channelHandlerContext);

            String host = fullHttpRequest.headers().get("host");

            String url = "https://" + host + "";

            originHttp.setUrl(url);
            originHttp.setClientCert(HttpServerJob.clientP12+"");
            reqQueue.put(originHttp);

        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.channel().close();
        }

        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("connect release " + HttpServerJob.close.incrementAndGet());
            super.channelInactive(ctx);
        }
    }

}